#include "WuDong.h"
#include "WinApi.h"
#include "KmdHost.h"
#include "Utils.h"
#include <mutex>
#include "ImageHost.h"
#include <shlwapi.h>
#include <numeric>
struct WuDongContext
{
    // 1. Windows API
    WinApi winapi;

    KmdHost kmdhost;

    ImageHost imagehost;

    std::map<std::wstring, long> vkmap;

    // work path
    std::wstring currPath;
};

const int small_block_size = 10;

static std::mutex s_mutex_to_top;

WuDong::WuDong()
{
    // 将进程默认 DPI 感知设置为系统 DPI 感知
    ::SetProcessDPIAware();

    m_context = new WuDongContext();

    // 初始化目录
    wchar_t buff[MAX_PATH];
    ::GetCurrentDirectoryW(MAX_PATH, buff);
    m_context->currPath = buff;
    m_context->imagehost.currPath_ = m_context->currPath;
    m_context->kmdhost.currPath_ = m_context->currPath;

    // 初始化键码表
    std::map<std::wstring, long> &_vkmap = m_context->vkmap;
    _vkmap[L"back"] = VK_BACK;
    _vkmap[L"ctrl"] = VK_CONTROL;
    _vkmap[L"alt"] = 18;
    _vkmap[L"shift"] = VK_SHIFT;
    _vkmap[L"win"] = VK_LWIN;
    _vkmap[L"space"] = L' ';
    _vkmap[L"tab"] = VK_TAB;
    _vkmap[L"esc"] = VK_CANCEL;
    _vkmap[L"enter"] = L'\r';
    _vkmap[L"up"] = VK_UP;
    _vkmap[L"down"] = VK_DOWN;
    _vkmap[L"left"] = VK_LEFT;
    _vkmap[L"right"] = VK_RIGHT;
    _vkmap[L"f1"] = VK_F1;
    _vkmap[L"f2"] = VK_F2;
    _vkmap[L"f3"] = VK_F3;
    _vkmap[L"f4"] = VK_F4;
    _vkmap[L"f5"] = VK_F5;
    _vkmap[L"f6"] = VK_F6;
    _vkmap[L"f7"] = VK_F7;
    _vkmap[L"f8"] = VK_F8;
    _vkmap[L"f9"] = VK_F9;
    _vkmap[L"f10"] = VK_F10;
    _vkmap[L"f11"] = VK_F11;
    _vkmap[L"f12"] = VK_F12;
}

WuDong::~WuDong()
{
    delete m_context;
}

void WuDong::innerCall(std::function<void()> func)
{
    if (m_mode == 101)
    {
        std::lock_guard<std::mutex> lock(s_mutex_to_top);
        func();
    }
    else
    {
        func();
    }
}

std::wstring WuDong::Ver()
{
    return L"0.4.2.0";
}

void WuDong::SetPath(const wchar_t *path, long *ret)
{
    wstring fpath = path;
    replacew(fpath, L"/", L"\\");
    if (fpath.find(L'\\') != wstring::npos && ::PathFileExistsW(fpath.data()))
    {
        m_context->currPath = fpath;
        m_context->imagehost.currPath_ = m_context->currPath;
        m_context->kmdhost.currPath_ = m_context->currPath;
        *ret = 1;
    }
    else
    {

        if (!fpath.empty() && fpath[0] != L'\\')
            fpath = m_context->currPath + L'\\' + fpath;
        else
            fpath = m_context->currPath + fpath;
        if (::PathFileExistsW(fpath.data()))
        {
            m_context->currPath = path;
            m_context->imagehost.currPath_ = m_context->currPath;
            m_context->kmdhost.currPath_ = m_context->currPath;
            *ret = 1;
        }
        else
        {
            setlog("path '%s' not exists", fpath.data());
            *ret = 0;
        }
    }
}

void WuDong::GetPath(std::wstring &ret)
{
    ret = m_context->currPath;
}

void WuDong::Delay(long mis_min, long min_add)
{
    bool bRet = Delays(mis_min, mis_min + min_add);
}

void WuDong::BindWindow(long hwnd, const wchar_t *display, const wchar_t *mouse, const wchar_t *keypad, long mode, long *ret)
{
    if (m_context->kmdhost.IsBind())
        m_context->kmdhost.UnBindWindow();
    *ret = m_context->kmdhost.BindWindow(hwnd, display, mouse, keypad, mode);
}
void WuDong::UnBindWindow(long *ret)
{
    *ret = m_context->kmdhost.UnBindWindow();
}
void WuDong::GetBindWindow(long *ret)
{
    *ret = m_context->kmdhost.GetBindWindow();
}

void WuDong::IsBind(long *ret)
{
    *ret = m_context->kmdhost.IsBind();
}

void WuDong::GetCursorPos(long *x, long *y, long *ret)
{
    innerCall([&]()
              { *ret = m_context->kmdhost._bkmouse->GetCursorPos(*x, *y); });
}

void WuDong::MoveR(long x, long y, long *ret)
{
    innerCall([&]()
              { *ret = m_context->kmdhost._bkmouse->MoveR(x, y); });
}

void WuDong::MoveTo(long x, long y, long *ret)
{
    innerCall([&]()
              { *ret = m_context->kmdhost._bkmouse->MoveTo(x, y); });
}

void WuDong::MoveToEx(long x, long y, long w, long h, long *ret)
{
    innerCall([&]()
              { *ret = m_context->kmdhost._bkmouse->MoveToEx(x, y, w, h); });
}

void WuDong::LeftClick(long *ret)
{
    innerCall([&]()
              { *ret = m_context->kmdhost._bkmouse->LeftClick(); });
}

void WuDong::LeftDoubleClick(long *ret)
{
    innerCall([&]()
              { *ret = m_context->kmdhost._bkmouse->LeftDoubleClick(); });
}

void WuDong::LeftDown(long *ret)
{
    innerCall([&]()
              { *ret = m_context->kmdhost._bkmouse->LeftDown(); });
}

void WuDong::LeftUp(long *ret)
{
    innerCall([&]()
              { *ret = m_context->kmdhost._bkmouse->LeftUp(); });
}

void WuDong::MiddleClick(long *ret)
{
    innerCall([&]()
              { *ret = m_context->kmdhost._bkmouse->MiddleClick(); });
}

void WuDong::MiddleDown(long *ret)
{
    innerCall([&]()
              { *ret = m_context->kmdhost._bkmouse->MiddleDown(); });
}

void WuDong::MiddleUp(long *ret)
{
    innerCall([&]()
              { *ret = m_context->kmdhost._bkmouse->MiddleUp(); });
}

void WuDong::RightClick(long *ret)
{
    innerCall([&]()
              { *ret = m_context->kmdhost._bkmouse->RightClick(); });
}

void WuDong::RightDown(long *ret)
{
    innerCall([&]()
              { *ret = m_context->kmdhost._bkmouse->RightDown(); });
}

void WuDong::RightUp(long *ret)
{
    innerCall([&]()
              { *ret = m_context->kmdhost._bkmouse->RightUp(); });
}

void WuDong::WheelDown(long *ret)
{
    innerCall([&]()
              { *ret = m_context->kmdhost._bkmouse->WheelDown(); });
}

void WuDong::WheelUp(long *ret)
{
    innerCall([&]()
              { *ret = m_context->kmdhost._bkmouse->WheelUp(); });
}

void WuDong::KeyDown(long vk_code, long *ret)
{
    innerCall([&]()
              { *ret = m_context->kmdhost._keypad->KeyDown(vk_code); });
}

void WuDong::KeyDownChar(const wchar_t *vk_code, long *ret)
{
    innerCall([&]()
              {
    auto nlen = wcslen(vk_code);
    *ret = 0;
    if (nlen > 0)
    {
        wstring s = vk_code;
        wstring2lower(s);
        long vk = m_context->vkmap.count(s) ? m_context->vkmap[s] : vk_code[0];
        *ret = m_context->kmdhost._keypad->KeyDown(vk);
    } });
}

void WuDong::KeyUp(long vk_code, long *ret)
{
    innerCall([&]()
              { *ret = m_context->kmdhost._keypad->KeyUp(vk_code); });
}

void WuDong::KeyUpChar(const wchar_t *vk_code, long *ret)
{
    innerCall([&]()
              {
        auto nlen = wcslen(vk_code);
        *ret = 0;
        if (nlen > 0)
        {
            wstring s = vk_code;
            wstring2lower(s);
            long vk = m_context->vkmap.count(s) ? m_context->vkmap[s] : vk_code[0];
            *ret = m_context->kmdhost._keypad->KeyUp(vk);
        } });
}

void WuDong::KeyPress(long vk_code, long *ret)
{
    innerCall([&]()
              { *ret = m_context->kmdhost._keypad->KeyPress(vk_code); });
}

void WuDong::KeyPressChar(const wchar_t *vk_code, long *ret)
{
    innerCall([&]()
              {
    auto nlen = wcslen(vk_code);
    *ret = 0;
    if (nlen > 0)
    {
        // setlog(vk_code);
        wstring s = vk_code;
        wstring2lower(s);
        long vk = m_context->vkmap.count(s) ? m_context->vkmap[s] : vk_code[0];
        *ret = m_context->kmdhost._keypad->KeyPress(vk);
    } });
}

// 抓取指定区域(x1, y1, x2, y2)的图像, 保存为file
void WuDong::Capture(long x1, long y1, long x2, long y2, const wchar_t *file_name, long *ret)
{
    *ret = 0;

    if (m_context->kmdhost.IsBind() && m_context->kmdhost.RectConvert(x1, y1, x2, y2))
    {
        if (!m_context->kmdhost.requestCapture(x1, y1, x2 - x1, y2 - y1, m_context->imagehost._src))
        {
            setlog("error requestCapture");
        }
        else
        {
            m_context->imagehost.set_offset(x1, y1);

            *ret = m_context->imagehost.Capture(file_name);
        }
    }
}

// 比较指定坐标点(x,y)的颜色
void WuDong::CmpColor(long x, long y, const wchar_t *color, DOUBLE sim, long *ret)
{
    // LONG rx = -1, ry = -1;
    long tx = x + small_block_size, ty = y + small_block_size;
    *ret = 0;
    if (m_context->kmdhost.IsBind() && m_context->kmdhost.RectConvert(x, y, tx, ty))
    {
        if (!m_context->kmdhost.requestCapture(x, y, small_block_size, small_block_size, m_context->imagehost._src))
        {
            setlog("error requestCapture");
        }
        else
        {
            m_context->imagehost.Capture(L"wudong.png");
            m_context->imagehost.set_offset(x, y);
            *ret = m_context->imagehost.CmpColor(x, y, color, sim);
        }
    }
}

// 查找指定区域内的颜色
void WuDong::FindColor(long x1, long y1, long x2, long y2, const wchar_t *color, DOUBLE sim, long dir, long *x, long *y, long *ret)
{

    *ret = 0;
    *x = *y = -1;

    if (m_context->kmdhost.IsBind() && m_context->kmdhost.RectConvert(x1, y1, x2, y2))
    {
        if (!m_context->kmdhost.requestCapture(x1, y1, x2 - x1, y2 - y1, m_context->imagehost._src))
        {
            setlog("error requestCapture");
        }
        else
        {
            m_context->imagehost.set_offset(x1, y1);
            *ret = m_context->imagehost.FindColor(color, sim, dir, *x, *y);
        }
    }
}
// 获取(x,y)的颜色
void WuDong::GetColor(long x, long y, std::wstring &ret)
{
    color_t cr;
    auto tx = x + small_block_size, ty = y + small_block_size;
    if (m_context->kmdhost.IsBind() && m_context->kmdhost.RectConvert(x, y, tx, ty))
    {
        if (m_context->kmdhost.requestCapture(x, y, small_block_size, small_block_size, m_context->imagehost._src))
        {
            m_context->imagehost.set_offset(x, y);
            cv::Vec4b bgr = m_context->imagehost._src.at<cv::Vec4b>(0, 0);
            cr = color_t(bgr[0], bgr[1], bgr[2]);
        }
        else
        {
            setlog("error requestCapture");
        }
    }
    else
    {
        // setlog("")
    }

    ret = cr.towstr();
}

void WuDong::FindPic(long x1, long y1, long x2, long y2, const wchar_t *files, double threshold, long *retx1, long *rety1, long *retx2, long *rety2, long *ret)
{
    *ret = 0;
    *retx1 = *rety1 = *retx2 = *rety2 = -1;

    if (m_context->kmdhost.IsBind() && m_context->kmdhost.RectConvert(x1, y1, x2, y2))
    {
        if (!m_context->kmdhost.requestCapture(x1, y1, x2 - x1, y2 - y1, m_context->imagehost._src))
        {
            setlog("error requestCapture");
        }
        else
        {
            m_context->imagehost.set_offset(x1, y1);
            *ret = m_context->imagehost.FindPic(files, threshold, *retx1, *rety1, *retx2, *rety2);
        }
    }
}
void WuDong::LoadPic(const wchar_t *file_name, long *ret)
{
    *ret = m_context->imagehost.LoadPic(file_name);
}

void WuDong::FreePic(const wchar_t *file_name, long *ret)
{
    *ret = m_context->imagehost.FreePic(file_name);
}

void WuDong::LoadMemPic(const wchar_t *file_name, void *data, long size, long *ret)
{
    *ret = m_context->imagehost.LoadMemPic(file_name, data, size);
}

void WuDong::ClearDict(long *ret)
{
    *ret = m_context->imagehost.ClearDict();
}

void WuDong::UseDict(const wchar_t *dict_name, long *ret)
{
    *ret = m_context->imagehost.UseDict(dict_name);
}

void WuDong::FindStr(long x1, long y1, long x2, long y2, const wchar_t *strs, double threshold, long *retx1, long *rety1, long *retx2, long *rety2, long *ret)
{
    *ret = 0;
    *retx1 = *rety1 = *retx2 = *rety2 = -1;

    if (m_context->kmdhost.IsBind() && m_context->kmdhost.RectConvert(x1, y1, x2, y2))
    {
        if (!m_context->kmdhost.requestCapture(x1, y1, x2 - x1, y2 - y1, m_context->imagehost._src))
        {
            setlog("error requestCapture");
        }
        else
        {
            m_context->imagehost.set_offset(x1, y1);
            *ret = m_context->imagehost.FindStr(strs, threshold, *retx1, *rety1, *retx2, *rety2);
        }
    }
}

void WuDong::Ocr(long x1, long y1, long x2, long y2, double threshold, std::vector<std::wstring> &ret_str, std::vector<cv::Rect> &rects)
{
    if (m_context->kmdhost.IsBind() && m_context->kmdhost.RectConvert(x1, y1, x2, y2))
    {
        if (!m_context->kmdhost.requestCapture(x1, y1, x2 - x1, y2 - y1, m_context->imagehost._src))
        {
            setlog("error requestCapture");
        }
        else
        {
            m_context->imagehost.set_offset(x1, y1);
            auto ocr_results = m_context->imagehost.Ocr(threshold, rects);
            ret_str = ocr_results;
            // ret_str = std::accumulate(ocr_results.begin(), ocr_results.end(), std::wstring(L""),
            //                           [](const std::wstring &acc, const std::wstring &ocr)
            //                           {
            //                               return acc + ocr;
            //                           });
        }
    }
}

std::wstring WuDong::JointResult(std::vector<std::wstring> &ret_str)
{
    return std::accumulate(ret_str.begin(), ret_str.end(), std::wstring(L""),
                           [](const std::wstring &acc, const std::wstring &ocr)
                           {
                               return acc + ocr;
                           });
}

void WuDong::DrawTestMat(const std::vector<cv::Rect> &rects, cv::Mat &mat)
{
    m_context->imagehost._src.copyTo(mat);
    for (auto &rect : rects)
    {
        cv::rectangle(mat, rect, cv::Scalar(0, 255, 0), 2);
    }
}

void WuDong::Sleep(long millseconds, long *ret)
{
    ::Sleep(millseconds);
    *ret = 1;
}

void WuDong::EnumWindow(long parent, const wchar_t *title, const wchar_t *class_name, long filter, std::wstring &ret)
{
    std::unique_ptr<wchar_t> retstring(new wchar_t[MAX_PATH * 200]);
    memset(retstring.get(), 0, sizeof(wchar_t) * MAX_PATH * 200);
    m_context->winapi.EnumWindow((HWND)parent, title, class_name, filter, retstring.get());
    //*retstr=_bstr_t(retstring);
    ret = retstring.get();
}

void WuDong::EnumWindowByProcess(const wchar_t *process_name, const wchar_t *title, const wchar_t *class_name, long filter, std::wstring &ret)
{
    std::unique_ptr<wchar_t> retstr(new wchar_t[MAX_PATH * 200]);
    memset(retstr.get(), 0, sizeof(wchar_t) * MAX_PATH * 200);
    m_context->winapi.EnumWindow((HWND)0, title, class_name, filter, retstr.get(), process_name);
    //*retstring=_bstr_t(retstr);

    ret = retstr.get();
}

void WuDong::EnumProcess(const wchar_t *name, std::wstring &ret)
{
    std::unique_ptr<wchar_t> retstr(new wchar_t[MAX_PATH * 200]);
    memset(retstr.get(), 0, sizeof(wchar_t) * MAX_PATH * 200);
    m_context->winapi.EnumProcess(name, retstr.get());
    //*retstring=_bstr_t(retstr);
    ret = retstr.get();
}

void WuDong::FindWindow(const wchar_t *class_name, const wchar_t *title, long *ret)
{
    *ret = m_context->winapi.FindWindow(class_name, title);
}

void WuDong::FindWindowByProcess(const wchar_t *process_name, const wchar_t *class_name, const wchar_t *title, long *ret)
{
    m_context->winapi.FindWindowByProcess(class_name, title, *ret, process_name);
}

void WuDong::FindWindowByProcessId(long process_id, const wchar_t *class_name, const wchar_t *title, long *ret)
{
    m_context->winapi.FindWindowByProcess(class_name, title, *ret, NULL, process_id);
}

void WuDong::FindWindowEx(long parent, const wchar_t *class_name, const wchar_t *title, long *ret)
{
    *ret = m_context->winapi.FindWindowEx(parent, class_name, title);
}

void WuDong::GetWindowRect(long hwnd, long *x1, long *y1, long *x2, long *y2, long *ret)
{
    RECT winrect;
    *ret = ::GetWindowRect((HWND)hwnd, &winrect);
    *x1 = winrect.left;
    *y1 = winrect.top;
    *x2 = winrect.right;
    *y2 = winrect.bottom;
}

void WuDong::MoveWindow(long hwnd, long x, long y, long *ret)
{
    RECT winrect;
    ::GetWindowRect((HWND)hwnd, &winrect);
    int width = winrect.right - winrect.left;
    int hight = winrect.bottom - winrect.top;
    *ret = ::MoveWindow((HWND)hwnd, x, y, width, hight, false);
}

void WuDong::SetClientSize(long hwnd, long width, long hight, long *ret)
{
    *ret = m_context->winapi.SetWindowSize(hwnd, width, hight);
}

void WuDong::SetWindowSize(long hwnd, long width, long height, long *ret)
{
    *ret = m_context->winapi.SetWindowSize(hwnd, width, height, 1);
}
